﻿#include<stdio.h>
#include<unistd.h>
#include<uview_keycode.h>
#include<gui.h>
#include<gui_window.h>
#include"terminal.h"
#include"terminal_console.h"
#include"terminal_cmd.h"

sh_console_t console;

#define NO_SCROLL 0

//from buffer get char
 void terminal_console_get_char(char *ch,int x,int y)
{
    if(console.cur_pos+y*console.columns+x>console.buffer+console.buffsz)
        return ;
    *ch=console.cur_pos[y*console.columns+x]; //save to buffer
}

//set cahr to buffer 
 void terminal_console_set_char(char ch,int x,int y)
{
    if(console.cur_pos+y*console.columns+x>console.buffer+console.buffsz)
        return ;
    console.cur_pos[y*console.columns+x]=ch; //save to buffer
}

void terminal_console_set_chars(char ch,int count,int x,int y)
{
    int tx=x,ty=y;
    while(count-->0)
    {
        terminal_console_set_char(ch,tx,ty);
        tx++;
        if(tx>=console.columns)
        {tx=0;ty++;}
        #if NO_SCROLL
            if(ty>=console.rows)
                ty=console.rows;
        #endif
    }
}

void terminal_console_get_chars(char *buff,int count,int x,int y)
{
    int tx=x,ty=y;
    char *tb=buff;
    while(count-->0)
    {
        terminal_console_get_char(tb,x,y);
        tx++;
        if(tx>console.columns)
        {
            tx=0;
            ty++;
        }
        #if NO_SCROLL
            if(ty>=console.rows)
                ty=console.rows;
        #endif

        if(*tb) //no '\0' just contiunate run
            tb++;
    }
}

void terminal_console_get_key(int keycode,int kmod)
{
    int j=0;
    char *q=NULL;
    int cx=0;
    int cy=0;

    //ctrl kmod
    if(kmod&UVIEW_KMOD_CTRL)
    {
        if(keycode==UVIEW_KEY_UP) 
        {
            scroll_screen(CONSOLE_SCROLL_UP,1,0,1);
            return 0;
        }
        else if(keycode==UVIEW_KEY_DOWN)
        {
            scroll_screen(CONSOLE_SCROLL_DOWN,1,0,1);
            return 0;
        } //ctrl + C
        else if(keycode==UVIEW_KEY_C||keycode==UVIEW_KEY_c)
        {
            char buff[2]={0};
            buff[0]='\003';
            console.outstr("^C");
            write(pty.fd_master,buff,strlen(buff));
            return 0;
        }
    }

    switch(keycode)
    {
        case UVIEW_KEY_UP:
            focus_cursor();
            cmd_buff_select(-1); //select to last cmd
            break;
        case UVIEW_KEY_DOWN:
            focus_cursor();
            cmd_buff_select(1);
            break;
        case UVIEW_KEY_NUMLOCK:
        case UVIEW_KEY_CAPSLOCK:
        case UVIEW_KEY_SCROLLOCK:
        case UVIEW_KEY_RSHIFT:
        case UVIEW_KEY_LSHIFT:
        case UVIEW_KEY_RALT:
        case UVIEW_KEY_LALT:
            break;    
        case UVIEW_KEY_LEFT:
            focus_cursor();
            if(cmdman->cmd_pos>cmdman->cmd_line)
            {
                --cmdman->cmd_pos;
                move_cursor_off(-1,0);
            }
            break;
        case UVIEW_KEY_RIGHT:
            focus_cursor();
            if((cmdman->cmd_pos-cmdman->cmd_line)<cmdman->cmd_line)
            {
                ++cmdman->cmd_pos;
                move_cursor_off(1,0);
            }
            break;
        case UVIEW_KEY_ENTER:
            focus_cursor();
            move_cursor_off(cmdman->cmd_len-(cmdman->cmd_pos-cmdman->cmd_line),0);
            console.outchar('\n');
            cmdman->cmd_line[cmdman->cmd_len]=0;
            cmdline_check();
            break;
        case UVIEW_KEY_BACKSPACE:
            focus_cursor();
            if(cmdman->cmd_pos>cmdman->cmd_line)
            {   
                //at the end
                if(cmdman->cmd_pos>=cmdman->cmd_line+cmdman->cmd_len)
                {
                     --cmdman->cmd_pos;
                     *cmdman->cmd_pos='\0';
                     console.outchar('\b');
                }
                else 
                {
                    //at the middle
                    --cmdman->cmd_pos;
                    //get cursor postion and save
                    get_cursor(&cx,&cy);
                    //remove character after current position
                    j=strlen(cmdman->cmd_pos);
                    while(j--)
                    {
                        //clear targe numbers chars after current cursor postion
                        console.outchar(' ');
                    }
                    //back to original position
                    move_cursor(cx,cy);
                    
                    //update command line cache
                    //all data afer cur position are move front
                    for (q = cmdman->cmd_pos; q < cmdman->cmd_line + cmdman->cmd_len; q++)
                        *q = *(q + 1);
                    
                    //move front one postion
                    move_cursor_off(-1,0);

                    //get current pos
                    get_cursor(&cx,&cy);
                    console.outstr(cmdman->cmd_pos);
                    move_cursor(cx,cy);
                }
                cmdman->cmd_len--;
            }
            break;
            default:    //custom keyboard input character
                focus_cursor();
                //at the end,direct insert and show
                if(cmdman->cmd_pos>=cmdman->cmd_line+cmdman->cmd_len)
                {
                    *cmdman->cmd_pos=keycode;
                    console.outchar(keycode);
                }
                else    //at the middle
                { 
                    //all char data are back move 
                    for(q=cmdman->cmd_line+cmdman->cmd_len;q>cmdman->cmd_pos;q--)
                    {
                        *q=*(q-1);
                        *(q-1)='\0';
                    }
                    get_cursor(&cx,&cy);
                    //insert data on current postion
                    *cmdman->cmd_pos=keycode;
                    console.outstr(cmdman->cmd_pos); 
                    move_cursor(cx+1,cy); //cursor back move
                }
                cmdman->cmd_pos++;
                cmdman->cmd_len++;
                break;
    }
    return 0;
}


void draw_chars_to_window()
{
    int bx=0,by=0,x=0,y=0;
    char ch=0;
    for(by=0;by<console.rows;by++)
    {
        for(bx=0;bx<console.columns;bx++)
        {
            terminal_console_get_char(&ch,bx,by);
            
            if(ch>=0x20&&ch<=0x7e)
            {
                x=bx*console.text_width;
                y=by*console.text_height;

                //draw to window 
                terminal_window_show_char(x,y,ch,console.font_color);
            }
        }
    }
}

static inline void terminal_console_flush()
{
    //clear
    terminal_window_rectfill(0,0,console.width,console.height,console.background_color);

    //load char from  buffer
    draw_chars_to_window();

    //window refresh
    terminal_window_update(0,0,console.width,console.height);

    draw_cursor();
}

static inline void terminal_console_flush_area(int x0,int y0,int x1,int y1)
{
    //clear
    terminal_window_rectfill(x0,y0,x1-x0,y1-y0,console.background_color);

    //load char from buffer
    draw_chars_to_window();

    //window refresh
    terminal_window_update(x0,y0,x1-x0,y1-y0);

    draw_cursor();
}


static void terminal_console_flush_rect(int x,int y,int w,int h)
{
    //clear background
    terminal_window_rectfill(x,y,w,h,console.background_color);
    //draw chars to window
    draw_chars_to_window();
    //refresh window 
    terminal_window_update(x,y,x+w,y+h);
    //draw cursor
    draw_cursor();
}

static void terminal_console_clear()
{
    //clear buffer 
    memset(console.buffer,0,console.buffsz);

    //reset buffer point
    console.cur_pos=console.buffer;

    //reset cursor to start
    cursor.x=0;
    cursor.y=0;
    draw_cursor();

    //clear window
    terminal_window_rectfill(0,0,console.width,console.height,console.background_color);
    terminal_window_update(0,0,console.width,console.height);
}

//select character(args: character postion)
void terminal_console_select_char(int cx,int cy)
{
    char ch=0;
    //get char data
    terminal_console_get_char(&ch,cx,cy);
    int x=cx*console.text_width,y=cy*console.text_height;
    gui_color_t backcolor=(0xffffff - (console.background_color & 0xffffff)) | (0xff << 24);
    gui_color_t fontcolor = (0xffffff - (console.font_color & 0xffffff)) | (0xff << 24);

    terminal_window_rectfill(x,y,console.text_width,console.text_height,backcolor);

    if(ch>=0x20&&ch<=0x7e)
    {
        terminal_window_show_char(x,y,ch,fontcolor);
    }
}

int can_scroll_down()
{
    if(console.cur_pos<(console.buffer+console.buffsz)-console.rows*console.columns)
        return 1;
    return 0;
}

int can_scroll_up()
{
    if(console.cur_pos>console.buffer)
    {
        return -1;
    }
    return 0;
}

void terminal_console_region_chars(int x0, int y0, int x1, int y1)
{
    int x, y;
    int cx, cy; // 字符坐标

    /* 计算刷新区域 */
    int left, right, top, bottom;
    top = min(y0, y1);
    bottom = max(y0, y1);
    /* 取整对齐 */
    top = top / console.text_height * console.text_height;
    bottom = bottom / console.text_height * console.text_height + console.text_height;
    
    /* 计算行数 */
    int lines = (bottom - top) / console.text_height;
    if (lines > 1) {    /* 选取多行，就需要刷新多行 */
        if (y1 > y0) {  /* 第一个点位于上方，那么，第一行的左边就是第一个点 */
            left = x0;
        } else {    /* 第二个点位于上方，那么，第一行的左边就是第二个点 */
            left = x1;
        }
        /* 第一行从左最小到右边框 */
        right = console.width;
        left = left / console.text_width * console.text_width;
        y = top;
        for (x = left; x < right; x += console.text_width) {
            cx = x / console.text_width;
            cy = y / console.text_height;
            /* 选中某个字符 */
            terminal_console_select_char(cx, cy);    
        }
        y += console.text_height;
        if (lines > 2) { /* 大于2行 */
            /* 整行都选择 */
            left = 0;
            right = console.width;
            while (lines > 2) {
                for (x = left; x < right; x += console.text_width) {
                    cx = x / console.text_width;
                    cy = y / console.text_height;
                    /* 选中某个字符 */
                    terminal_console_select_char(cx, cy);    
                }
                y += console.text_height;
                lines--;
            } 
        }
        if (y1 > y0) {
            right = x1; /* 第二个点位于下方，那么，最后一行的右边就是第二个点 */
        } else {
            right = x0; /* 第一个点位于下方，那么，最后一行的右边就是第一个点 */
        }
        /* 从左边框到最右最大 */
        left = 0;
        right = right / console.text_width * console.text_width + console.text_width;
        /* 最后一行 */
        for (x = left; x < right; x += console.text_width) {
            cx = x / console.text_width;
            cy = y / console.text_height;
            /* 选中某个字符 */
            terminal_console_select_char(cx, cy);    
        }
    } else {
        /* 最左到最右 */
        left = min(x0, x1);
        right = max(x0, x1);
        left = left / console.text_width * console.text_width;
        right = right / console.text_width * console.text_width + console.text_width;
        /* 向下对齐 */
        for (y = top; y < bottom; y += console.text_height) {
            for (x = left; x < right; x += console.text_width) {
                cx = x / console.text_width;
                cy = y / console.text_height;
                /* 选中某个字符 */
                terminal_console_select_char(cx, cy);    
            }
        }
    }
    if (bottom - top > console.text_height) {    /* 选取多行，就需要刷新多行 */
        left = 0;
        right = console.width;
    } else {
        left = min(x0, x1);
        right = max(x0, x1);
        left = left / console.text_width * console.text_width;
        right = right / console.text_width * console.text_width + console.text_width;
    }
    /* 只刷新一次 */
    terminal_window_update(left, top, right, bottom);
}

void terminal_console_flush2(int mx0,int my0,int mx1,int my1)
{
    int cx0,cy0,cx1,cy1;
    cx0=mx0/console.text_width;
    cy0=my0/console.text_height;
    cx1=mx1/console.text_width;
    cy1=my1/console.text_height;
    if((cx0!=cx1)||(cy0!=cy1))
    {
       terminal_console_flush_rect(0,min(cy0,cy1)*console.text_height,console.width,(abs(cy1-cy0)+1)*console.text_height);
    }
}

static inline void terminal_console_clear_area(int x,int y,uint32_t width,uint32_t height)
{
    //clear background
    terminal_window_rectfill(x,y,width,height,console.background_color);
    terminal_window_update(x,y,width,height);
}


//no refresh
static void terminal_console_outchar(char ch)
{
    terminal_cursor_clean();
    int counts=0;
    switch(ch)
    {
        case '\n':
            //set a char on cursor positon
            terminal_console_set_char('\n',cursor.x,cursor.y);
            //can enter
            if(can_scroll_down())
            {
                move_cursor(0,cursor.y+1);
            }
        break;
        case '\b':
            terminal_console_set_char(' ',cursor.x,cursor.y);
            //back one step
            cursor.x--;
            cursor_pos_check();

            terminal_console_set_char(0,cursor.x,cursor.y);
            draw_cursor();
            break;
        case '\t':
             /* how much char distinct with current position */
            counts = ((cursor.x + 4) & (~(4 - 1))) - cursor.x;
            while (counts--) {
                terminal_console_set_char(' ', cursor.x, cursor.y);
                move_cursor(cursor.x + 1, cursor.y);    
            }
			break;
        case '\r':
            break;
        default:
            terminal_console_set_char(ch,cursor.x,cursor.y);
            cursor_pos_check();
            move_cursor(cursor.x+1,cursor.y);
            break;
    }
}

void terminal_console_output_visual(char ch,int x,int y)
{
    if(ch>=0x20&&ch<=0x7e)
    {
        terminal_window_show_char(x,y,ch,console.font_color);
        terminal_window_update(x,y,x+console.text_width,y+console.text_height);
    }
}

static void terminal_console_outstr(char *str)
{
    while(*str!='\0')
    {
        terminal_console_outchar(*str);
        str++;
    }
}


/**
 * scroll_screen - 向上或者向下滚动屏幕
 * @dir: 滚动方向
 * @lines 滚动几行
 * @cursorx: 滚动后光标x是否改变
 * @cursory: 滚动后光标y是否改变
 *
 */
void scroll_screen(int dir, int lines, int cursorx, int cursory)
{
    if (dir == CONSOLE_SCROLL_UP)
    {
        // 判断是否能滚屏
        if (!can_scroll_up())
        {
            // 如果不能向下滚屏就返回
            return;
        }

        // 清空背景
        terminal_window_rectfill(0, 0,
                            console.width, console.height, console.background_color);

        // 修改显存起始位置
        console.cur_pos -= console.columns * lines;

        // 把字符全部加载到窗口
        draw_chars_to_window();

        /* 刷新全部 */
        terminal_window_update(0, 0,
                         console.width, console.height);
        if (cursorx)
            cursor.x = 0;
        if (cursory)
        {
            cursor.y += lines;
            if (cursor.y > console.rows - 1)
            {
                // cursor.y = console.rows - 1;
                cursor.visual = 0;
            }
            else
            {
                cursor.visual = 1;
            }
        }
        // 修改光标位置
        draw_cursor();
    }
    else if (dir == CONSOLE_SCROLL_DOWN)
    {
        // 判断是否能滚屏
        if (!can_scroll_down())
        {
            // 如果不能向下滚屏就返回
            return;
        }
        // 清空背景
        terminal_window_rectfill(0, 0,
                            console.width, console.height, console.background_color);

        // 修改显存起始位置
        console.cur_pos += console.columns * lines;

        // 把字符全部加载到窗口
        draw_chars_to_window();
        /* 刷新全部 */
        terminal_window_update(0, 0,
                         console.width, console.height);

        // if (!accord) {
        if (cursorx)
            cursor.x = 0;

        if (cursory)
        {
            cursor.y -= lines;
            if (cursor.y < 0)
            {
                // cursor.y = 0;
                cursor.visual = 0;
            }
            else
            {
                cursor.visual = 1;
            }
        }
        //}
        // 绘制光标
        draw_cursor();
    }
}

void terminal_console_set_backcolor(gui_color_t color)
{
    console.background_color=color;
}

void terminal_console_set_frontcolor(gui_color_t color)
{
    console.font_color=color;
}

int terminal_console_printf(const char *fmt,...)
{
    char buff[BUFFSZ];
    va_list arg=NULL;
    va_start(arg,fmt);
    vsprintf(buff,fmt,arg);

    console.outstr(buff);

    return 0;
}

void terminal_console_putchar(char ch)
{
    console.outchar(ch);
}

int terminal_console_init()
{
    if(terminal_window_init()<0)
    {
        dprintf("[terminal] init window failed\n");
        return -1;
    }
    uint32_t width=0,height=0;
    terminal_window_size(&width,&height);
    console.width=width;
    console.height=height;
    console.text_width=CON_TEXT_WIDTH;
    console.text_height=CON_TEXT_HEIGHT;
    console.background_color=CON_BACK_COLOR;
    console.font_color=CON_FRONT_COLOR;
    console.cursors=0;
    
    console.window=terminal_win;

    console.clear=terminal_console_clear;
    console.clear_area=terminal_console_clear_area;
    console.outchar=terminal_console_outchar;
    console.outstr=terminal_console_outstr;
    console.flush=terminal_console_flush;  
    console.columns=console.width/console.text_width;
    console.columns_w=console.text_width*console.columns;  
    console.rows=console.height/console.text_height;
    console.rows_height=console.text_height*console.rows;
    console.buffsz=console.rows*console.columns*CON_FRAME_NR;
    console.buffer=malloc(console.buffsz);
    if(!console.buffer)
    {
        dprintf("[terminal] init screen buffer failed\n");
        return ;
    }
    memset(console.buffer,0,console.buffsz);
    console.cur_pos=console.buffer;

    terminal_window_rectfill(0,0,console.width,console.height,CON_BACK_COLOR);

    terminal_cmdman_init();
    
    return 0;
}

int exit_console()
{
    free(console.buffer);
    terminal_window_exit();
    exit_cmdman();
    return 0;
}